
#include "HrkModel.h"

ShaderPack  CHrkModel::m_skinProgram;

CHrkModel::CHrkModel()
{
	m_root_node = NULL;
	
}

CHrkModel::~CHrkModel()
{
}
int CHrkModel::FindBoneIndex( const TCHAR* name )
{
	for( int i = 0;i<m_bone_list.Count();i++ ){
		if( m_bone_list[i]->m_name.compare(name) == 0)
			return i;

	}
	return -1;
}


bool CHrkModel::LoadFile( const TCHAR* filename )
{
#ifdef _UNICODE
	CHAR filename1[MAX_PATH];
	wcstombs( filename1,filename,MAX_PATH );
	TiXmlDocument doc(filename1);
#else
	TiXmlDocument doc(filename);
#endif

	bool loaded = doc.LoadFile();
	
	if( !loaded )
		return false;

	TiXmlElement* root_element = doc.RootElement();

	const char* name = root_element->Attribute("name") ;
	
	if( strcmp( name,"NODE_ROOT" ) != 0  ){
		return NULL;
	}
	
	if( m_root_node ){
		m_root_node->Release();	
	}
	
	m_root_node = new CHrkNode;
	m_root_node->m_ob_type = HRK_ROOT;
	
	ParseMaterial( root_element );
	ParseNode( root_element,m_root_node );

	m_bone_list.clear();
	CollectBoneList( m_root_node );

	m_mesh_list.clear();
	CollectMeshList( m_root_node );

	PostLoadFile();

	if( !m_skinProgram.GetShader() ){
		m_skinProgram.CreateAndCompileShaders("GLSL\\skin_mesh.vs",NULL);
	}

	return true;
}

void CHrkModel::PostLoadFile()
{
	int numMesh = m_mesh_list.Count();
	for( int i = 0;i<numMesh;i++ )
	{
		CHrkMesh* mesh = m_mesh_list[i];

		int numSkinBone =  mesh->m_skinBonesVec.Count() ;

		for( int j = 0;j<numSkinBone;j++ )
		{	
			tSkinBone& skinBone = mesh->m_skinBonesVec[j];
			int indexNum = skinBone.numSkinVert;
			
			if(mesh->m_weightVec.Count() == 0){

				int numVerts = mesh->m_posVec.Count();
				mesh->m_weightVec.resize( numVerts );
				
				GLmat4f m;

				for( int k = 0;k<4;k++)
					m[k].Clear();

				mesh->m_weightVec.Fill( m );
				
				mesh->m_boneIndexVec.resize( numVerts );
					
				for( int k = 0;k<4;k++)
					m[k]  = GLvec4f(-1.f,-1.f,-1.f,-1.f);

				
				mesh->m_boneIndexVec.Fill( m );
			}

			int boneIndex = FindBoneIndex( skinBone.boneName.c_str());
			skinBone.pBone = m_bone_list[boneIndex];
	
			for( int k = 0;k<indexNum;k++ ){

				int slot = -1;	
				int vertIndex = skinBone.vIndices[k];
				float weight = skinBone.wArrays[k];

				if( weight < 1e-4 ){
					continue;
				}
			
				float * bmat = &mesh->m_boneIndexVec[vertIndex][0];
				float * wmat = &mesh->m_weightVec[vertIndex][0];

				for( int kk = 0;kk<16;kk++ ){
					if( bmat[kk] ==-1 ){
						slot = kk;
						break;
					}
				}
				
				assert( slot >= 0 );
				assert( slot <16 );
			
				bmat[slot] = j;
				wmat[slot] = weight;
				
			}

			skinBone.numSkinVert = 0;

			delete[] skinBone.vIndices;
			skinBone.vIndices = NULL;
			delete[] skinBone.wArrays;
			skinBone.wArrays = NULL;
		}
	}
}

GLmaterial* CHrkModel::FindMaterial( const TCHAR* name )
{
	for( int i = 0;i<m_mat_list.Count();i++ ){
		if( m_mat_list[i]->m_name.compare( name ) == 0 ){
			return m_mat_list[i];
		}
	}

	return NULL;
}

void CHrkModel::ParseMaterial( TiXmlElement* root )
{
	//GLmaterial;
	TiXmlElement* e = root->FirstChildElement("Materials");
	
	TiXmlElement* e1 = e->FirstChildElement("Material");
	while(e1){
		
		TiXmlElement* e2 = e1->FirstChildElement("Texture");
		GLmaterial* mat = new GLmaterial; 
		mat->m_name = e1->Attribute("name");
		
		while(e2){
			const char* texname = e2->Attribute("name");
			GLtexture* tex = GLtexture::LoadTexture( texname );
			e2 = e2->NextSiblingElement("Texture");
			mat->AddTexLayer( tex );
		}

		m_mat_list.push_back(mat);
		e1 = e1->NextSiblingElement("Material");
	}
}

void CHrkModel::CollectBoneList( CHrkNode* node )
{	
	if( node->m_ob_type == HRK_BONE ){
		m_bone_list.push_back( node );
	}
	else if( node->m_ob_type == HRK_MESH ){
		CHrkMesh* mesh = (CHrkMesh*)node->m_object;
		if(mesh->m_skinBonesVec.Count() == 0){
			m_bone_list.push_back( node );
		}
	}
	
	int nChilds = node->m_child_list.Count();
	for( int i = 0;i<nChilds;i++ ){
		CollectBoneList( node->m_child_list[i] );
	}
}


void CHrkModel::CollectMeshList( CHrkNode* node )
{
	if( node->m_ob_type == HRK_MESH ){
		m_mesh_list.push_back( static_cast<CHrkMesh*>( node->m_object ));
	}

	
	int nChilds = node->m_child_list.Count();
	for( int i = 0;i<nChilds;i++ ){
		CollectMeshList( node->m_child_list[i] );
	}
}

void CHrkModel::ParseNode( TiXmlElement* elem,CHrkNode* node )
{
	CHrkNode* node1 = NULL;

	const char* s1 = elem->Attribute("name");
	const char* s = elem->Attribute( "type" );
	
	if(!s)
		return;
	
	HrkObType ob_type =  StrType2Hrk( s );

	node1 = MakeHrkNode( ob_type );

	if( ob_type == HRK_MESH ){
		dynamic_cast<CHrkMesh*>(node1->m_object)->m_pModel = this;
	}

	node1->ParseScript( elem );
	node->AttachChild( node1 );

	TiXmlElement* child = elem->FirstChildElement("Node");

	while( child ){
		ParseNode( child,node1 );
		child = child->NextSiblingElement("Node");
	}	
}
#include <mmsystem.h>
void CHrkModel::Update()
{
	m_root_node->UpdateTransform();
}

void CHrkModel::DrawSkinMeshGPU()
{
	int meshCount = m_mesh_list.Count();
	for( int i= 0;i< meshCount;i++ ){
		CHrkMesh*mesh = m_mesh_list[i];
		if( mesh->m_posVec.Count() == 0)
			continue;
	
		glPushMatrix();

		glMultMatrixf( mesh->m_mat_comb.Base());

		int numWeight = mesh->m_weightVec.Count();

		GLint indexBone = -1;
		GLint indexWeight = -1;
		GLint indexBoneMat = -1;

		if( numWeight ){
			m_skinProgram.UseProgram();
		}
	
		if( mesh->m_vcVec.Count() == 0 ){
			GL_BeginArray( (GLvec3f*)&mesh->m_posVec[0],(GLvec3f*)&mesh->m_normalVec[0],(GLvec2f*)&mesh->m_tcVec[0],NULL );		
		}
		else{
			GL_BeginArray( (GLvec3f*)&mesh->m_posVec[0],(GLvec3f*)&mesh->m_normalVec[0],(GLvec2f*)&mesh->m_tcVec[0],(GLvec4f*)&mesh->m_vcVec[0]);
		}
		
		if( numWeight ){

			indexBoneMat = glGetUniformLocation( m_skinProgram.GetShader(),"boneMat");
			int numBones = mesh->m_skinBonesVec.Count();
			for( int j = 0;j<numBones;j++ ){

				GLmat4f m = mesh->m_mat_comb*mesh->m_skinBonesVec[j].offsetMatrix;
				m = m*mesh->m_skinBonesVec[j].pBone->m_mat_comb;	
				glUniformMatrix4fvARB( indexBoneMat+j,1,GL_FALSE,m.Base() );
			}

 			indexBone = glGetAttribLocationARB(m_skinProgram.GetShader(), "bone");			
			glEnableVertexAttribArrayARB(indexBone);
			glVertexAttribPointerARB(indexBone,1,GL_FLOAT,false,0,&mesh->m_boneIndexVec[0]);

			indexWeight = glGetAttribLocationARB(m_skinProgram.GetShader(), "weight");
			glEnableVertexAttribArrayARB(indexWeight);
			glVertexAttribPointerARB(indexWeight,1,GL_FLOAT,false,0,&mesh->m_weightVec[0]);

		}

		int nSubMeshes = mesh->m_subMeshesVec.Count();
		for( int j = 0;j<nSubMeshes;j++ ){
			CHrkSubMesh* subMesh = mesh->m_subMeshesVec[j];
			int nIndexBuffers= subMesh->m_indexBuffers.Count();
			GLmaterial* mat = subMesh->m_pMaterial;
			if( mat ){
				if( mat->m_texList.Count()>0) 
					glBindTexture( GL_TEXTURE_2D,mat->m_texList[0]->m_id );
				else 
					glDisable( GL_TEXTURE_2D );
			}
			
			for( int k = 0;k<nIndexBuffers;k++ ){
				CHrkIndexBuffer* ib = subMesh->m_indexBuffers[k];
				int type = ib->m_faceType;
				
				if( type == FT_TRILIST ){
					GL_DrawTriListArray( &ib->m_faceIndices[0],ib->m_faceIndices.Count());
				}
				else if( type == FT_TRISTRIP ){
					GL_DrawTriStripArray( &ib->m_faceIndices[0],ib->m_faceIndices.Count());
				}
			}
		}

		if( numWeight ){

			glDisableVertexAttribArrayARB(indexBone);
			glDisableVertexAttribArrayARB(indexWeight);
			m_skinProgram.UnUseProgram();
		}
		

		GL_EndArray();	

		glPopMatrix();
	}
}

void CHrkModel::DrawSkinMeshCPU()
{
	int meshCount = m_mesh_list.Count();
	
	for( int i= 0;i< meshCount;i++ ){
		CHrkMesh*mesh = m_mesh_list[i];
		if( mesh->m_posVec.Count() == 0)
			continue;
	
		glPushMatrix();

		int numWeight = mesh->m_weightVec.Count();
		
		if( numWeight == 0) {
			glMultMatrixf( mesh->m_mat_comb.Base());

		}

		GLint indexBone = -1;
		GLint indexWeight = -1;
		GLint indexBoneMat = -1;

		static STLvector<GLvec3f> posVec;
		static STLvector<GLvec3f> nrmVec;

		int numVertices = mesh->m_posVec.Count();

		GLvec3f* pos = (GLvec3f*)&mesh->m_posVec[0];
		GLvec3f* nrm = (GLvec3f*)&mesh->m_normalVec[0];

		if( posVec.Count() < numVertices){
			posVec.resize( numVertices );
			nrmVec.resize( numVertices );
		}

		if( numWeight ){
			
			indexBoneMat = glGetUniformLocation( m_skinProgram.GetShader(),"boneMat");
			int numBones = mesh->m_skinBonesVec.Count();	
	
			GLmat4f  root_mat = m_root_node->m_mat_comb;
			root_mat.Invert();
			
			for( int j = 0;j<numVertices;j++ )
			{
				const GLvec3f& p = mesh->m_posVec[j];
				const GLvec3f& n = mesh->m_normalVec[j];

				GLvec3f p1,n1;

				posVec[j].Clear();
				nrmVec[j].Clear();

				float * bmat = &mesh->m_boneIndexVec[j][0];
				float * wmat = &mesh->m_weightVec[j][0];
				
				for( int k = 0;k<16;k++ ){
					int b = (int)bmat[k];
					float w = wmat[k];

					if( b!=-1 ){
						if( w >1e-5 ){	
						
							p1 = root_mat.TransformVec3(p);
							p1 = mesh->m_mat_comb.TransformVec3( p1 );
							p1 = mesh->m_skinBonesVec[b].offsetMatrix.TransformVec3( p1 );	
							p1 = mesh->m_skinBonesVec[b].pBone->m_mat_comb.TransformVec3( p1 );		
							p1 *= w;			
							posVec[j] = posVec[j] + p1;		

							n1 = n;//mesh->m_mat_comb.TransformVec3( n );
							n1 = mesh->m_skinBonesVec[b].offsetMatrix.TransformVec3( n1 );	
							//n1 = mesh->m_skinBonesVec[b].pBone->m_mat_comb.TransformVec3( n1 );		
							n1 *= w;		
							
							nrmVec[j] = nrmVec[j] + n1;	
							nrmVec[j].Normalize();	
						}
					}
				}	
			}

			nrm = (GLvec3f*)&nrmVec[0];
			pos = (GLvec3f*)&posVec[0];
		}
	
		if( mesh->m_vcVec.Count() == 0 ){
			GL_BeginArray( pos,nrm,(GLvec2f*)&mesh->m_tcVec[0],NULL );		
		}
		else{
			GL_BeginArray( pos,nrm,(GLvec2f*)&mesh->m_tcVec[0],(GLvec4f*)&mesh->m_vcVec[0] );
		}
		
		int nSubMeshes = mesh->m_subMeshesVec.Count();
		for( int j = 0;j<nSubMeshes;j++ ){
			CHrkSubMesh* subMesh = mesh->m_subMeshesVec[j];
			int nIndexBuffers= subMesh->m_indexBuffers.Count();
			GLmaterial* mat = subMesh->m_pMaterial;
			if( mat ){
				if( mat->m_texList.Count()>0) 
					glBindTexture( GL_TEXTURE_2D,mat->m_texList[0]->m_id );
				else 
					glDisable( GL_TEXTURE_2D );
			}
			
			for( int k = 0;k<nIndexBuffers;k++ ){
				CHrkIndexBuffer* ib = subMesh->m_indexBuffers[k];
				int type = ib->m_faceType;
				
				if( type == FT_TRILIST ){
					GL_DrawTriListArray( &ib->m_faceIndices[0],ib->m_faceIndices.Count());
				}
				else if( type == FT_TRISTRIP ){
					GL_DrawTriStripArray( &ib->m_faceIndices[0],ib->m_faceIndices.Count());
				}
			}
		}

		GL_EndArray();	

		glPopMatrix();
	}
}

void CHrkModel::Draw()
{
	float position[4] = { 0.f,00.f,0.f,0.f };
	float direction[4] = { 0.f,-1.f,0.f,1.f };
	float diffuse[4] = {1.f,1.f,1.f,1.f};

	position[0] = 0.f;
	position[1] = 0.f;
	position[2] = -100.f;
	position[3] = 1.f;

	direction[0] = 0.f;
	direction[1] = 1.f;
	direction[2] = 1.f;

	glLightfv(GL_LIGHT1,GL_POSITION,position );
	//glLightfv(GL_LIGHT1,GL_SPOT_DIRECTION,direction );
	glLightfv(GL_LIGHT1,GL_DIFFUSE,diffuse);
	glEnable( GL_LIGHT1);
	
	position[0] = 0.f;
	position[1] = 100.f;
	position[2] = 0.f;
	position[3] = 1.f;

	direction[0] = 0.f;
	direction[1] = -1.f;
	direction[2] = -1.f;

	glLightfv(GL_LIGHT0,GL_POSITION,position );
	glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,direction );
	glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
	glEnable( GL_LIGHT0);

	glMaterialfv( GL_FRONT,GL_AMBIENT_AND_DIFFUSE,diffuse);
	glEnable ( GL_LIGHTING );

	DrawSkinMeshCPU();
	//DrawSkinMeshGPU();

}
